package com.xuzimian.globaldemo.spring.springmvc.controller;

import com.google.common.collect.Lists;
import com.xuzimian.globaldemo.spring.springmvc.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ModelAttribute 主要是用在JSP这类带视图的请求方法中。
 * <p>
 * 1.@ModelAttribute 注解用在方法上，则用于设置参数，它会在执行处理钱将参数设置到Model中。
 * * a.如果@ModelAttrribute 注解设置了value属性值， 则将其作为参数名，返回值作为参数值设置到Model.
 * * b.而如果方法含有Model,Map或者ModelMap类型的参数，则可以直接将需要设置的参数设置上去。
 * * c.如果既没有设置value属性，也没有Model类型的参数，则根据返回值类型解析出参数名，返回值作为参数值设置到Model.
 * <p>
 * 2.@ModelAttribute 注解用在方法参数上,则标识需要使用指定的ArgumentResolver来解析参数，将Model上对象的key的value值设置到该方法参数上。
 * <p>
 * 3.@ModelAttribute 注解用在方法上，同时该方法上有@RequestMapping注解，则表明该方法是一个http处理器，就不会提前执行了。
 * <p>
 * ---------------------------------------------------------------------------------------------------------------------------------
 * <p>
 * * 如何继承公共方法的方案。
 * * 1.可以当做父类给子类yController继承，从而公用那些@ModelAttribute，@InitBinder 注解的方法。
 * * 2.在类上添加@ControllerAdvice 注解，就可以将@InitBinder,@ModelAttribute 注解的方法共享给所有的controller(Handler)
 * 从Spring Mvc4.0开始 @ControllerAdvice 注解 添加了value,basePackages,basePackageClasses,assigableType和 annotations
 * 五个参数，可以通过这些参数选择将这些方法用于哪些Controller,而不是必须所有的Controller.
 *
 * @program: global-demo
 * @description: @ModelAttribute
 * @author: xzm
 * @create: 2019-04-24 16:09
 **/
@Controller
@RequestMapping("/model")
public class ModelAttributeController {

    @CrossOrigin("http://domain2.com") //@CrossOrigin 注解用于在控制器方法上启用跨域请求
    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public String index() {
        return "hello Web";
    }

    /**
     * 注解有返回值的方法
     * 使用@ModelAttribute注解的方法会先于@RequestMapping注解的方法执行，如果有多个方法用@ModelAttribute标记，则会根据标记顺序依次执行。
     * 注意：设置这个注解之后可以直接在视图页面（JSP）使用users这个对象（List）集合
     * 或者：在其他有映射http请求的方法（GetMapping,PostMapping）执行前执行,可以在这些方法中使用Model 对象获取这些值
     * <p>
     * 执行http请求前给Model设置("users",users)
     *
     * @return
     */
    @ModelAttribute("users")
    public List<User> setUsers() {
        List<User> users = Lists.newArrayList();
        users.add(new User("xzm1", 18, "12345"));
        users.add(new User("xzm2", 18, "123456"));
        return users;
    }

    /**
     * 注解无返回值的方法
     * 使用@ModelAttribute注解的方法会先于@RequestMapping注解的方法执行，如果有多个方法用@ModelAttribute标记，则会根据标记顺序依次执行。
     * <p>
     * 执行http请求前给Model设置("abc",abc)
     *
     * @param abc
     * @param model
     */
    @ModelAttribute
    public void setAttribute(String abc, Model model) {
        model.addAttribute("attributeName", abc);
    }


    /**
     * 执行http请求前给Model设置("string","hello")
     *
     * @return
     */
    @ModelAttribute
    public String setAttribute() {
        return "hello";
    }


    /**
     * 它的作用是将该绑定的对象以“user”为名称添加到模型对象中供视图页面(JSP)展示使用
     * 主要有两种使用场景：1.在视图中创建一个对象 2.从视图中获取一个对象
     *
     * @param user
     * @return
     */
    @GetMapping("/user.jsp")
    public String getUser(@ModelAttribute("user") User user, Model model) {
        System.out.println(model.asMap().get("user").toString());
        System.out.println(model.asMap().get("users222").toString());
        System.out.println(model.asMap().get("attributeName").toString());
        return "user.jsp";
    }


    /**
     * 返回的Json对象中包含:users,attributeName,均是在@ModelAttribute 注解方法中存入的。
     * 注意：返回类型为Model(Model,ModelMap 实际类型均为BindingAwareModelMap)时，即使在方法上注解了@ResponseBody,
     * 也会将该请求处理为一个返回jsp页面。其原因是在 HandlerMethodReturnValueHandlerComposite的 调用selectHandler
     * 寻找 返回值处理器（HandlerMethodReturnValueHandler）时
     * private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
     * 		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
     * 		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
     * 			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
     * 				continue;
     *                        }
     * 			if (handler.supportsReturnType(returnType)) {
     * 				return handler;
     *            }        * 		}
     * 		return null;
     * }
     * 调用的 handler.supportsReturnType(returnType) 这一方法通过返回值来匹配对应的handler，优先匹配的是
     * ModelMethodProcessor，查看supportsReturnType方法：
     * public boolean supportsReturnType(MethodParameter returnType) {
     * 		return Model.class.isAssignableFrom(returnType.getParameterType());
     *        }
     *  其就是通过检查返回值类型是否Model 接口类型，所以当成有视图的方法处理了
     *
     *
     * @param model
     * @return
     */
    @GetMapping(value = "/users")
    @ResponseBody
    public Map<String, Object> modelAttribute(Model model) {
        return model.asMap();
    }


}
